package cn.itcast.server;

import cn.itcast.protocol.MessageCodecSharable;
import cn.itcast.protocol.ProcotolFrameDecoder;
import cn.itcast.server.handler.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

/**
 * 我们的业务就是 聊天室 然后客户端得先登录
 * 我们要用netty来做一个聊天室，然后双方的协议用我们自定义的那一个MessageCodec
 * 然后各种业务其实就是写各种处理器(入站/出站处理器)(且根据不同的生命周期(read事件时触发/连接建立时触发...) 去处理不同的业务处理
 * 然后客户端跟服务器端是通过channel来连接然后在这之间传递数据的，所以如果你要找到具体的客户端/服务器去你就得找到它们具体的channel是啥
 *
 * 服务器端就是给客户端们中转的 像websocket
 * 我们之前做聊天的时候 websocket也是这样 websocket服务端就是给连接上来的客户端它们之间推数据(通讯的)的 长连接
 *
 *
 * initChannel主要是装载加载的时候用到的         客户端/服务端都要加载组件的时候都要用的
 * channelActive这种是建立连接的时候要用到的(被调用的)      一般用于客户端建立连接后要做xxx
 * 所以顾名思义 channelInactive 就是断开连接的时候要用到的(被调用的)  一般用于服务器端接收到客户端要断开连接的时候做xxx
 *
 */
@Slf4j
public class ChatServer {
    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();
        //这里都是抽取出来的 一个handler实例可以被多个eventLoop(调用者)去共享使用的,因为它们里面没有共享的变量不会有并发问题
        LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
        MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable(); //换成可以@Sharable注解的类
        LoginRequestMessageHandler LOGIN_HANDLER = new LoginRequestMessageHandler();//自定义处理器 处理登录业务
        ChatRequestMessageHandler CHAT_HANDLER = new ChatRequestMessageHandler();//自定义处理器 处理发消息的业务
        GroupChatRequestMessageHandler GROUP_CHAT_HANDLER =new GroupChatRequestMessageHandler();
        GroupCreateRequestMessageHandler GROUP_CREAT_HANDLER =new GroupCreateRequestMessageHandler();
        GroupJoinRequestMessageHandler GROUP_JOIN_HANDLER =new GroupJoinRequestMessageHandler();
        GroupMembersRequestMessageHandler GROUP_MEMBERS_HANDLER =new GroupMembersRequestMessageHandler();
        GroupQuitRequestMessageHandler GROUP_QUIT_HANDLER =new GroupQuitRequestMessageHandler();


        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.group(boss, worker);
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                //入站处理器默认是从head -> tail这样的方向去找然后依次执行的 别忘了 所以顺序记得排一下
                //出站处理器默认是从tail -> head这样的方向去找然后依次执行的 别忘了 所以顺序记得排一下
                @Override
                protected void initChannel(SocketChannel ch) throws Exception { //accept()方法发生的时候会被调用 去装载处理器 accept肯定就是eventLoop调用的啦
                    //处理半包闭包问题的处理器  用我们自定义的处理器
                    ch.pipeline().addLast(new ProcotolFrameDecoder());
                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(MESSAGE_CODEC); //看一下我们自己写的编解码器这个编解码器不没有共享变量的所以是不存在多线程并发问题的 所以可以抽取公用
                    //假死: 假如程序一直在运行 但是假如网络断开了 程序还是活着的网络断了 那么客户端的信息发不进来服务器的信息也发不出去 但是程序并没有感知
                    //这样就一直无端端的在浪费服务器的资源 这种就是假死 netty已经给了一个解决方案  IdleStateHandler 处理器
                    //IdleStateHandler处理器 参数分别代表什么意思: 1.多久没收到读信息后会触发读事件 2.多久没写过数据后会触发写事件 3.读写都空闲了多长时间后触发读写事件
                    //下面的配置: 如果有客户端连接但是5秒内没有读事件发生 那么将会触发一个IdleStateHandler内的事件(IdleState#READER_IDLE事件) 那么肯定就也有其他事件 像写事件 读写事件等
                    //那么这些事件谁来处理呢？肯定是我们自己啦 因为我们要有我们自己的操作 所以写一个处理器 如下ChannelDuplexHandler
                    ch.pipeline().addLast(new IdleStateHandler(5,0,0));
                    //ChannelDuplexHandler 是出站处理器 也是 入站处理器
                    ch.pipeline().addLast(new ChannelDuplexHandler(){

                        //用户事件触发器
                        @Override
                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                            IdleStateEvent event = (IdleStateEvent) evt;
                            //处理上面说的 假死读 事件
                            if(event.state() == IdleState.READER_IDLE){
                                log.debug("已经5s 没接收到数据了");
                            }
                        }
                    });
                    ch.pipeline().addLast(LOGIN_HANDLER);
                    ch.pipeline().addLast(CHAT_HANDLER);
                    ch.pipeline().addLast(GROUP_CHAT_HANDLER);
                    ch.pipeline().addLast(GROUP_CREAT_HANDLER);
                    ch.pipeline().addLast(GROUP_JOIN_HANDLER);
                    ch.pipeline().addLast(GROUP_MEMBERS_HANDLER);
                    ch.pipeline().addLast(GROUP_QUIT_HANDLER);
                }
            });
            Channel channel = serverBootstrap.bind(8080).sync().channel(); //sync()阻塞住等到服务器连接建立完然后往下执行
            channel.closeFuture().sync(); //这个也是阻塞的只有当真的关闭了才往下走
        } catch (InterruptedException e) {
            log.error("server error", e);
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

}
